Dynamic Wakes 2¶
Horizontally homogeneous wake propagation¶
For horizontally homogeneous timeseries input data (i.e., dependency on time and optionally also on z, but not on x, y coordinates), foxes offers a simplified way to compute dynamic wake propagation. This in principle works by following a flow trace backwards in time from each point of interest, and identifying it with a wake trajectory if it approaches the vicinity of a rotor. For horizontally homogeneous inflow the steps of these traces are independent of the evaluation point.
Similarly to the prevously discussed DynamicWakes approach, this concept only works if
either all states fall into a single chunk,
or the
Iterativealgorithm is used for the calculation.
These are the inlcudes for this example:
In [1]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["animation.html"] = "jshtml"
import foxes
import foxes.variables as FV
import foxes.constants as FC
We create a case with a regular 3 x 3 wind farm layout:
In [2]:
states = foxes.input.states.Timeseries(
data_source="timeseries_100.csv.gz",
output_vars=[FV.WS, FV.WD, FV.TI, FV.RHO],
var2col={FV.WS: "ws", FV.WD: "wd", FV.TI: "ti"},
fixed_vars={FV.RHO: 1.225, FV.TI: 0.07},
)
farm = foxes.WindFarm()
foxes.input.farm_layout.add_grid(
farm,
xy_base=np.array([0.0, 0.0]),
step_vectors=np.array([[1000.0, 0], [0, 800.0]]),
steps=(3, 3),
turbine_models=["DTU10MW"],
verbosity=0,
)
algo = foxes.algorithms.Iterative(
farm,
states,
rotor_model="grid25",
wake_models=["Bastankhah2014_linear_lim_k004"],
wake_frame="timelines",
partial_wakes="rotor_points",
verbosity=1,
)
Notice the wake frame choice timelines, which is a pre-defined instance of the class Timelines from the model book.
Let’s run the wind farm calculation:
In [3]:
with foxes.Engine.new("process"):
farm_results = algo.calc_farm()
Algorithm Iterative: Iteration 0
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 16 chunks using 16 processes
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [00:00<00:00, 90.96it/s]
Algorithm Iterative: Iteration 1
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 16 chunks using 16 processes
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [00:00<00:00, 46.86it/s]
DefaultConv: Convergence check
REWS: delta = 2.404e-01, lim = 1.000e-06 -- FAILED
TI : delta = 0.000e+00, lim = 1.000e-07 -- OK
CT : delta = 3.099e-03, lim = 1.000e-07 -- FAILED
Algorithm Iterative: Iteration 2
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 16 chunks using 16 processes
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [00:00<00:00, 42.56it/s]
DefaultConv: Convergence check
REWS: delta = 0.000e+00, lim = 1.000e-06 -- OK
TI : delta = 0.000e+00, lim = 1.000e-07 -- OK
CT : delta = 0.000e+00, lim = 1.000e-07 -- OK
Algorithm Iterative: Convergence reached.
Starting final run
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 16 chunks using 16 processes
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [00:00<00:00, 238.26it/s]
Notice the iterations and the convergence behaviour, resulting in less iterations than the previous DynamicWakes example. Now the farm results are ready:
In [4]:
farm_df = farm_results.to_dataframe()
print("\nFarm results data:\n")
print(farm_df[[FV.AMB_REWS, FV.REWS, FV.P]])
Farm results data:
AMB_REWS REWS P
state turbine
2023-07-07 12:00:00 0 6.0 6.000000 1532.700000
1 6.0 6.000000 1532.700000
2 6.0 6.000000 1532.700000
3 6.0 6.000000 1532.700000
4 6.0 6.000000 1532.700000
... ... ... ...
2023-07-07 13:39:00 4 6.0 4.806876 702.119290
5 6.0 4.806876 702.119290
6 6.0 4.458187 521.551830
7 6.0 4.458187 521.551817
8 6.0 4.458187 521.551830
[900 rows x 3 columns]
This timeseries has a time step of 1 minute. Let’s visualize the wake dynamics in an animation:
In [5]:
with foxes.Engine.new("process", chunk_size_points=3000):
fig, axs = plt.subplots(
2, 1, figsize=(5.2, 7), gridspec_kw={"height_ratios": [3, 1]}
)
anim = foxes.output.Animator(fig)
# this adds the flow anomation to the upper panel:
of = foxes.output.FlowPlots2D(algo, farm_results)
anim.add_generator(
of.gen_states_fig_xy(
FV.WS,
resolution=30,
quiver_pars=dict(scale=0.013),
quiver_n=35,
xmax=5000,
ymax=5000,
fig=fig,
ax=axs[0],
vmin=0,
vmax=6,
title=None,
ret_im=True,
animated=True,
)
)
# This adds the REWS signal animation to the lower panel:
o = foxes.output.FarmResultsEval(farm_results)
anim.add_generator(
o.gen_stdata(
turbines=[4, 7],
variable=FV.REWS,
fig=fig,
ax=axs[1],
ret_im=True,
legloc="upper left",
animated=True,
)
)
# This adds turbine indices at turbine positions:
lo = foxes.output.FarmLayoutOutput(farm)
lo.get_figure(
fig=fig,
ax=axs[0],
title="",
annotate=1,
anno_delx=-120,
anno_dely=-60,
alpha=0,
)
ani = anim.animate()
plt.close()
print("done.")
print("Creating animation")
ani
Creating animation data
States 'Timeseries': Reading file /home/jonas/gits/wakes/foxes/foxes/data/states/timeseries_100.csv.gz
ProcessEngine: Calculating data at 33856 points for 100 states
ProcessEngine: Computing 192 chunks using 16 processes
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 192/192 [00:23<00:00, 8.28it/s]
done.
Creating animation
Out[5]:
For the fun of it, let’s re-run this case assuming the time step was 10 s instead of 1 min. We can do so by using the wake frame Timelines(dt_min=1/6), which is called timelines_10s in the model book:
In [6]:
with foxes.Engine.new("process", chunk_size_points=3000):
algo = foxes.algorithms.Iterative(
farm,
states,
rotor_model="grid25",
wake_models=["Bastankhah2014_linear_lim_k004"],
wake_frame="timelines_10s",
partial_wakes="rotor_points",
verbosity=1,
)
farm_results = algo.calc_farm()
fig, axs = plt.subplots(
2, 1, figsize=(5.2, 7), gridspec_kw={"height_ratios": [3, 1]}
)
anim = foxes.output.Animator(fig)
# this adds the flow anomation to the upper panel:
of = foxes.output.FlowPlots2D(algo, farm_results)
anim.add_generator(
of.gen_states_fig_xy(
FV.WS,
resolution=30,
quiver_pars=dict(scale=0.013),
quiver_n=35,
xmax=5000,
ymax=5000,
fig=fig,
ax=axs[0],
vmin=0,
title=lambda si, s: f"t = {si/6:3.2f} min",
ret_im=True,
animated=True,
)
)
# This adds the REWS signal animation to the lower panel:
o = foxes.output.FarmResultsEval(farm_results)
anim.add_generator(
o.gen_stdata(
turbines=[4, 7],
variable=FV.REWS,
fig=fig,
ax=axs[1],
ret_im=True,
legloc="upper left",
animated=True,
)
)
# This adds turbine indices at turbine positions:
lo = foxes.output.FarmLayoutOutput(farm)
lo.get_figure(
fig=fig,
ax=axs[0],
title="",
annotate=1,
anno_delx=-120,
anno_dely=-60,
alpha=0,
)
ani = anim.animate()
plt.close()
print("done.")
print("Creating animation")
ani
Algorithm Iterative: Iteration 0
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 16 chunks using 16 processes
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [00:00<00:00, 67.26it/s]
Algorithm Iterative: Iteration 1
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 16 chunks using 16 processes
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [00:00<00:00, 41.66it/s]
DefaultConv: Convergence check
REWS: delta = 1.996e-01, lim = 1.000e-06 -- FAILED
TI : delta = 0.000e+00, lim = 1.000e-07 -- OK
CT : delta = 2.568e-03, lim = 1.000e-07 -- FAILED
Algorithm Iterative: Iteration 2
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 16 chunks using 16 processes
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [00:00<00:00, 46.49it/s]
DefaultConv: Convergence check
REWS: delta = 6.094e-08, lim = 1.000e-06 -- OK
TI : delta = 0.000e+00, lim = 1.000e-07 -- OK
CT : delta = 9.119e-10, lim = 1.000e-07 -- OK
Algorithm Iterative: Convergence reached.
Starting final run
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 16 chunks using 16 processes
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [00:00<00:00, 342.89it/s]
Creating animation data
States 'Timeseries': Reading file /home/jonas/gits/wakes/foxes/foxes/data/states/timeseries_100.csv.gz
ProcessEngine: Calculating data at 33856 points for 100 states
ProcessEngine: Computing 192 chunks using 16 processes
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 192/192 [00:30<00:00, 6.23it/s]
done.
Creating animation
Out[6]: